/*****************************************************************************
 *
 *  $Id: main.c,v 6a6dec6fc806 2012/09/19 17:46:58 fp $
 *
 *  Copyright (C) 2007-2009  Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  This file is part of the IgH EtherCAT Master.
 *
 *  The IgH EtherCAT Master is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License version 2, as
 *  published by the Free Software Foundation.
 *
 *  The IgH EtherCAT Master is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
 *  Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along
 *  with the IgH EtherCAT Master; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  The license mentioned above concerns the source code only. Using the
 *  EtherCAT technology and brand is only permitted in compliance with the
 *  industrial property and similar rights of Beckhoff Automation GmbH.
 *
 ****************************************************************************/

#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>

/****************************************************************************/

#include "ecrt.h"

/****************************************************************************/

// Application parameters
#define FREQUENCY 1000
#define CLOCK_TO_USE CLOCK_REALTIME
/*#define MEASURE_TIMING*/
#define PRIORITY 1

#define NSEC_PER_SEC (1000000000L)
#define PERIOD_NS (NSEC_PER_SEC / FREQUENCY)
#define DIFF_NS(A, B) (((B).tv_sec - (A).tv_sec) * NSEC_PER_SEC + \
	(B).tv_nsec - (A).tv_nsec)

#define TIMESPEC2NS(T) ((uint64_t) (T).tv_sec * NSEC_PER_SEC + (T).tv_nsec)

// Optional features
#define CONFIGURE_PDOS  1
#define N 10

/****************************************************************************/

// EtherCAT
static ec_master_t *master = NULL;
static ec_master_state_t master_state = {};

static ec_domain_t *domain1 = NULL;
static ec_domain_state_t domain1_state = {};

static ec_slave_config_t *sc;
static ec_slave_config_state_t sc_ana_in_state = {};

// Timer
static unsigned int sig_alarms = 0;
static unsigned int user_alarms = 0;
/****************************************************************************/

// process data
static uint8_t *domain1_pd = NULL;
#define BusCouplerPos  0, 0
#define DigOutSlavePos 0, 1

#define Beckhoff_EK1100 0x00000002, 0x044c2c52
#define Beckhoff_EL2008 0x00000002, 0x07d83052

// offsets for PDO entries
static unsigned int o_dig_out[8], bo_dig_out[8];

const static ec_pdo_entry_reg_t domain1_regs[] = {
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7000, 1, &o_dig_out[0], &bo_dig_out[0]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7010, 1, &o_dig_out[1], &bo_dig_out[1]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7020, 1, &o_dig_out[2], &bo_dig_out[2]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7030, 1, &o_dig_out[3], &bo_dig_out[3]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7040, 1, &o_dig_out[4], &bo_dig_out[4]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7050, 1, &o_dig_out[5], &bo_dig_out[5]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7060, 1, &o_dig_out[6], &bo_dig_out[6]},
	{DigOutSlavePos, Beckhoff_EL2008,
		0x7070, 1, &o_dig_out[7], &bo_dig_out[7]},
	{}
};

static unsigned int counter = 0;
static unsigned int blink_out_0 = 0x01;
const struct timespec cycletime = {0, PERIOD_NS};

/*****************************************************************************/

struct timespec timespec_add(struct timespec time1, struct timespec time2)
{
	struct timespec result;

	if ((time1.tv_nsec + time2.tv_nsec) >= NSEC_PER_SEC) {
		result.tv_sec = time1.tv_sec + time2.tv_sec + 1;
		result.tv_nsec = time1.tv_nsec + time2.tv_nsec - NSEC_PER_SEC;
	} else {
		result.tv_sec = time1.tv_sec + time2.tv_sec;
		result.tv_nsec = time1.tv_nsec + time2.tv_nsec;
	}

	return result;
}

/*****************************************************************************/

#if CONFIGURE_PDOS

static ec_pdo_entry_info_t el2008_channels[] = {
	{0x7000, 1, 1}, // Value 1
	{0x7010, 1, 1}, // Value 2
	{0x7020, 1, 1}, // Value 3
	{0x7030, 1, 1}, // Value 4
	{0x7040, 1, 1}, // Value 4
	{0x7050, 1, 1}, // Value 4
	{0x7060, 1, 1}, // Value 4
	{0x7070, 1, 1}  // Value 4
};

static ec_pdo_info_t el2008_pdos[] = {
	{0x1600, 1, &el2008_channels[0]},
	{0x1601, 1, &el2008_channels[1]},
	{0x1602, 1, &el2008_channels[2]},
	{0x1603, 1, &el2008_channels[3]},
	{0x1604, 1, &el2008_channels[4]},
	{0x1605, 1, &el2008_channels[5]},
	{0x1606, 1, &el2008_channels[6]},
	{0x1607, 1, &el2008_channels[7]}
};

static ec_sync_info_t el2008_syncs[] = {
	{0, EC_DIR_OUTPUT, 8, el2008_pdos},
	{0xff}
};

#endif

/*****************************************************************************/

void check_domain1_state(void)
{
    ec_domain_state_t ds;

    ecrt_domain_state(domain1, &ds);

    if (ds.working_counter != domain1_state.working_counter)
        printf("Domain1: WC %u.\n", ds.working_counter);
    if (ds.wc_state != domain1_state.wc_state)
        printf("Domain1: State %u.\n", ds.wc_state);

    domain1_state = ds;
}

/*****************************************************************************/

void check_master_state(void)
{
	ec_master_state_t ms;

    ecrt_master_state(master, &ms);

    if (ms.slaves_responding != master_state.slaves_responding)
        printf("%u slave(s).\n", ms.slaves_responding);
    if (ms.al_states != master_state.al_states)
        printf("AL states: 0x%02X.\n", ms.al_states);
    if (ms.link_up != master_state.link_up)
        printf("Link is %s.\n", ms.link_up ? "up" : "down");

    master_state = ms;
}

/*****************************************************************************/

void check_slave_config_states(void)
{
	ec_slave_config_state_t s;

	ecrt_slave_config_state(sc, &s);

    if (s.al_state != sc_ana_in_state.al_state)
        printf("AnaIn: State 0x%02X.\n", s.al_state);

    if (s.online != sc_ana_in_state.online)
        printf("AnaIn: %s.\n", s.online ? "online" : "offline");
    if (s.operational != sc_ana_in_state.operational)
        printf("AnaIn: %soperational.\n",
                s.operational ? "" : "Not ");

    sc_ana_in_state = s;
}

/*****************************************************************************/
void cyclic_task_nanosleep(void)
{
	struct timespec wakeupTime;
#ifdef MEASURE_TIMING
	int i = 0;
	struct timeval tv[N];
#endif

    // get current time
	clock_gettime(CLOCK_TO_USE, &wakeupTime);
#ifdef MEASURE_TIMING
	while (i < N) {
#else
	while (1) {
#endif
		wakeupTime = timespec_add(wakeupTime, cycletime);
		clock_nanosleep(CLOCK_TO_USE, TIMER_ABSTIME, &wakeupTime, NULL);

		// receive process data
		ecrt_master_receive(master);
		ecrt_domain_process(domain1);

		// check process data state (optional)
		check_domain1_state();
		EC_WRITE_BIT(domain1_pd + o_dig_out[0], bo_dig_out[0],
				blink_out_0 ? 0x01 : 0x00);

		if (counter) {
			counter--;
		} else { // 1hz
			counter = FREQUENCY;
			blink_out_0 = !blink_out_0;
		}

		// check for master state (optional)
		check_master_state();
		// check for islave configuration state(s) (optional)
		check_slave_config_states();

		// send process data
		ecrt_domain_queue(domain1);

#ifdef MEASURE_TIMING
		gettimeofday(&tv[i++], NULL);
#endif
		ecrt_master_send(master);
	}

#ifdef MEASURE_TIMING
	sleep(3);
	for (i = 0; i < N; i++)
		printf("%d %u.%06u\n", i, tv[i].tv_sec, tv[i].tv_usec);
#endif
}

/****************************************************************************/

void signal_handler(int signum) {
    switch (signum) {
        case SIGALRM:
            sig_alarms++;
            break;
    }
}

/****************************************************************************/

int main(int argc, char **argv)
{
    struct sigaction sa;
    struct itimerval tv;

    // Requests an EtherCAT master for realtime operation.
    master = ecrt_request_master(0);
	if (!master)
		return -1;

	// Creates a new process data domain
    domain1 = ecrt_master_create_domain(master);
	if (!domain1)
		return -1;

#if CONFIGURE_PDOS
    // Obtains a slave configuration
	sc = ecrt_master_slave_config(master, DigOutSlavePos, Beckhoff_EL2008);
	if (!sc) {
        fprintf(stderr, "Failed to get slave configuration.\n");
        return -1;
	}

    // Configuring PDOs
	printf("Configuring PDOs...\n");
	if (ecrt_slave_config_pdos(sc, EC_END, el2008_syncs)) {
		fprintf(stderr, "Failed to configure PDOs.\n");
		return -1;
    }

	if (ecrt_domain_reg_pdo_entry_list(domain1, domain1_regs)) {
		fprintf(stderr, "OUT PDO entry registration failed!\n");
		return -1;
    }
#endif

    printf("Activating master...\n");
	if (ecrt_master_activate(master))
		return -1;

	domain1_pd = ecrt_domain_data(domain1);
	if (!domain1_pd)
		return -1;

	cyclic_task_nanosleep();
    return 0;
}

